"""
第三课：HTTP 协议与 RESTful API 实战练习
配合 03_http_restful_api.md 讲义使用

这是一个完整的 RESTful API 示例项目

运行方式：
cd /Users/baimu/PycharmProjects/fastApiProject3
uvicorn lessons.03_http_restful_api_practice:app --reload --port 8001

然后访问：
- API 文档: http://127.0.0.1:8001/docs
- 根路径: http://127.0.0.1:8001/
"""

from fastapi import FastAPI, HTTPException, status, Query, Path
from pydantic import BaseModel, EmailStr, Field
from typing import List, Optional
from datetime import datetime
from enum import Enum

# ========== 应用初始化 ==========

app = FastAPI(
    title="图书管理系统 API",
    description="一个完整的 RESTful API 示例，演示 HTTP 方法和状态码的使用",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
)

# ========== 枚举类型 ==========


class BookCategory(str, Enum):
    """图书分类"""

    FICTION = "fiction"
    SCIENCE = "science"
    HISTORY = "history"
    TECHNOLOGY = "technology"
    ART = "art"


class BookStatus(str, Enum):
    """图书状态"""

    AVAILABLE = "available"
    BORROWED = "borrowed"
    MAINTENANCE = "maintenance"


# ========== Pydantic 模型 ==========


class BookBase(BaseModel):
    """图书基础模型"""

    title: str = Field(..., min_length=1, max_length=200, description="书名")
    author: str = Field(..., min_length=1, max_length=100, description="作者")
    isbn: str = Field(..., pattern=r"^\d{13}$", description="ISBN号（13位数字）")
    category: BookCategory = Field(..., description="图书分类")
    price: float = Field(..., gt=0, description="价格（必须大于0）")
    description: Optional[str] = Field(None, max_length=1000, description="图书描述")


class BookCreate(BookBase):
    """创建图书时的模型"""

    pass


class BookUpdate(BookBase):
    """更新图书时的模型（所有字段必填）"""

    status: BookStatus = Field(default=BookStatus.AVAILABLE, description="图书状态")


class BookPatch(BaseModel):
    """部分更新图书时的模型（所有字段可选）"""

    title: Optional[str] = Field(None, min_length=1, max_length=200)
    author: Optional[str] = Field(None, min_length=1, max_length=100)
    isbn: Optional[str] = Field(None, pattern=r"^\d{13}$")
    category: Optional[BookCategory] = None
    price: Optional[float] = Field(None, gt=0)
    description: Optional[str] = Field(None, max_length=1000)
    status: Optional[BookStatus] = None


class Book(BookBase):
    """完整的图书模型（包含数据库字段）"""

    id: int = Field(..., description="图书ID")
    status: BookStatus = Field(default=BookStatus.AVAILABLE, description="图书状态")
    created_at: datetime = Field(..., description="创建时间")
    updated_at: datetime = Field(..., description="更新时间")

    class Config:
        json_schema_extra = {
            "example": {
                "id": 1,
                "title": "Python 编程从入门到实践",
                "author": "Eric Matthes",
                "isbn": "9787115428028",
                "category": "technology",
                "price": 89.0,
                "description": "Python 入门经典教材",
                "status": "available",
                "created_at": "2024-01-01T00:00:00",
                "updated_at": "2024-01-01T00:00:00",
            }
        }


class BorrowRecord(BaseModel):
    """借阅记录"""

    id: int
    book_id: int
    borrower_name: str
    borrower_email: EmailStr
    borrow_date: datetime
    return_date: Optional[datetime] = None
    status: str  # borrowed / returned


class BorrowCreate(BaseModel):
    """创建借阅记录"""

    borrower_name: str = Field(..., min_length=1, max_length=50)
    borrower_email: EmailStr


# ========== 模拟数据库 ==========

# 图书数据库
books_db: List[Book] = [
    Book(
        id=1,
        title="Python 编程从入门到实践",
        author="Eric Matthes",
        isbn="9787115428028",
        category=BookCategory.TECHNOLOGY,
        price=89.0,
        description="适合初学者的 Python 教程",
        status=BookStatus.AVAILABLE,
        created_at=datetime(2024, 1, 1),
        updated_at=datetime(2024, 1, 1),
    ),
    Book(
        id=2,
        title="三体",
        author="刘慈欣",
        isbn="9787536692930",
        category=BookCategory.FICTION,
        price=23.0,
        description="科幻小说经典之作",
        status=BookStatus.AVAILABLE,
        created_at=datetime(2024, 1, 2),
        updated_at=datetime(2024, 1, 2),
    ),
    Book(
        id=3,
        title="人类简史",
        author="尤瓦尔·赫拉利",
        isbn="9787508660752",
        category=BookCategory.HISTORY,
        price=68.0,
        description="从动物到上帝的人类发展史",
        status=BookStatus.BORROWED,
        created_at=datetime(2024, 1, 3),
        updated_at=datetime(2024, 1, 3),
    ),
]

# 借阅记录数据库
borrow_records_db: List[BorrowRecord] = []

# 自增 ID
next_book_id = 4
next_borrow_id = 1


# ========== 工具函数 ==========


def find_book_by_id(book_id: int) -> Optional[Book]:
    """根据ID查找图书"""
    for book in books_db:
        if book.id == book_id:
            return book
    return None


def find_book_by_isbn(isbn: str) -> Optional[Book]:
    """根据ISBN查找图书"""
    for book in books_db:
        if book.isbn == isbn:
            return book
    return None


# ========== API 端点 ==========

# ---------- 根路径 ----------


@app.get("/", tags=["根路径"])
async def root():
    """
    API 根路径
    
    返回 API 的基本信息和可用端点
    """
    return {
        "message": "欢迎使用图书管理系统 API",
        "version": "1.0.0",
        "docs": "/docs",
        "endpoints": {
            "books": "/api/v1/books",
            "health": "/health",
        },
    }


@app.get("/health", tags=["健康检查"])
async def health_check():
    """
    健康检查端点
    
    用于检查服务是否正常运行
    """
    return {
        "status": "healthy",
        "timestamp": datetime.now(),
        "books_count": len(books_db),
        "borrow_records_count": len(borrow_records_db),
    }


# ---------- 图书管理（CRUD操作）----------


@app.get(
    "/api/v1/books",
    response_model=List[Book],
    tags=["图书管理"],
    summary="获取图书列表",
    description="获取所有图书，支持分页、过滤和排序",
)
async def list_books(
    skip: int = Query(0, ge=0, description="跳过的记录数"),
    limit: int = Query(10, ge=1, le=100, description="每页显示的记录数"),
    category: Optional[BookCategory] = Query(None, description="按分类过滤"),
    status: Optional[BookStatus] = Query(None, description="按状态过滤"),
    search: Optional[str] = Query(None, min_length=1, description="搜索书名或作者"),
):
    """
    获取图书列表
    
    **功能：**
    - 分页：使用 skip 和 limit 参数
    - 过滤：按分类或状态过滤
    - 搜索：在书名和作者中搜索关键词
    
    **示例：**
    - `/api/v1/books?limit=5` - 获取前5本书
    - `/api/v1/books?category=technology` - 获取技术类图书
    - `/api/v1/books?search=Python` - 搜索Python相关的书
    """
    # 开始筛选
    filtered_books = books_db

    # 按分类过滤
    if category:
        filtered_books = [b for b in filtered_books if b.category == category]

    # 按状态过滤
    if status:
        filtered_books = [b for b in filtered_books if b.status == status]

    # 搜索
    if search:
        search_lower = search.lower()
        filtered_books = [
            b
            for b in filtered_books
            if search_lower in b.title.lower() or search_lower in b.author.lower()
        ]

    # 分页
    total = len(filtered_books)
    result = filtered_books[skip : skip + limit]

    # 在响应头中添加分页信息（实际应用中可以这样做）
    return result


@app.get(
    "/api/v1/books/{book_id}",
    response_model=Book,
    tags=["图书管理"],
    summary="获取单本图书",
    responses={
        200: {"description": "成功返回图书信息"},
        404: {"description": "图书不存在"},
    },
)
async def get_book(book_id: int = Path(..., gt=0, description="图书ID")):
    """
    根据ID获取图书详情
    
    **参数：**
    - book_id: 图书的唯一标识符
    
    **返回：**
    - 200: 返回图书详细信息
    - 404: 图书不存在
    """
    book = find_book_by_id(book_id)

    if book is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"图书 ID {book_id} 不存在",
        )

    return book


@app.post(
    "/api/v1/books",
    response_model=Book,
    status_code=status.HTTP_201_CREATED,
    tags=["图书管理"],
    summary="创建新图书",
    responses={
        201: {"description": "图书创建成功"},
        400: {"description": "请求数据错误"},
        409: {"description": "ISBN已存在"},
    },
)
async def create_book(book: BookCreate):
    """
    创建新图书
    
    **请求体：**
    ```json
    {
      "title": "书名",
      "author": "作者",
      "isbn": "9787115428028",
      "category": "technology",
      "price": 89.0,
      "description": "描述"
    }
    ```
    
    **返回：**
    - 201: 图书创建成功
    - 400: 数据格式错误（FastAPI 自动验证）
    - 409: ISBN 已存在
    """
    global next_book_id

    # 检查 ISBN 是否已存在
    existing_book = find_book_by_isbn(book.isbn)
    if existing_book:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"ISBN {book.isbn} 已存在（图书ID: {existing_book.id}）",
        )

    # 创建新图书
    now = datetime.now()
    new_book = Book(
        **book.dict(),
        id=next_book_id,
        status=BookStatus.AVAILABLE,
        created_at=now,
        updated_at=now,
    )

    books_db.append(new_book)
    next_book_id += 1

    return new_book


@app.put(
    "/api/v1/books/{book_id}",
    response_model=Book,
    tags=["图书管理"],
    summary="完整更新图书",
    responses={
        200: {"description": "更新成功"},
        404: {"description": "图书不存在"},
        409: {"description": "ISBN冲突"},
    },
)
async def update_book(
    book_id: int = Path(..., gt=0, description="图书ID"), book_data: BookUpdate = None
):
    """
    完整更新图书信息
    
    **注意：** 需要提供所有字段（完整替换）
    
    **与 PATCH 的区别：**
    - PUT: 完整替换，需要所有字段
    - PATCH: 部分更新，只需要更新的字段
    """
    # 查找图书
    book_index = None
    for i, book in enumerate(books_db):
        if book.id == book_id:
            book_index = i
            break

    if book_index is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail=f"图书 ID {book_id} 不存在"
        )

    # 检查 ISBN 冲突（如果修改了 ISBN）
    old_book = books_db[book_index]
    if book_data.isbn != old_book.isbn:
        existing_book = find_book_by_isbn(book_data.isbn)
        if existing_book:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"ISBN {book_data.isbn} 已被图书 ID {existing_book.id} 使用",
            )

    # 更新图书
    updated_book = Book(
        **book_data.dict(),
        id=book_id,
        created_at=old_book.created_at,
        updated_at=datetime.now(),
    )

    books_db[book_index] = updated_book

    return updated_book


@app.patch(
    "/api/v1/books/{book_id}",
    response_model=Book,
    tags=["图书管理"],
    summary="部分更新图书",
    responses={
        200: {"description": "更新成功"},
        404: {"description": "图书不存在"},
    },
)
async def patch_book(
    book_id: int = Path(..., gt=0, description="图书ID"), book_data: BookPatch = None
):
    """
    部分更新图书信息
    
    **优势：** 只需要提供要更新的字段
    
    **示例：** 只更新价格
    ```json
    {
      "price": 99.0
    }
    ```
    """
    # 查找图书
    book_index = None
    for i, book in enumerate(books_db):
        if book.id == book_id:
            book_index = i
            break

    if book_index is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail=f"图书 ID {book_id} 不存在"
        )

    # 获取要更新的字段
    update_data = book_data.dict(exclude_unset=True)

    # 检查 ISBN 冲突
    if "isbn" in update_data:
        new_isbn = update_data["isbn"]
        existing_book = find_book_by_isbn(new_isbn)
        if existing_book and existing_book.id != book_id:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"ISBN {new_isbn} 已被图书 ID {existing_book.id} 使用",
            )

    # 更新图书
    old_book = books_db[book_index]
    updated_book = old_book.copy(
        update={**update_data, "updated_at": datetime.now()}
    )

    books_db[book_index] = updated_book

    return updated_book


@app.delete(
    "/api/v1/books/{book_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    tags=["图书管理"],
    summary="删除图书",
    responses={
        204: {"description": "删除成功"},
        404: {"description": "图书不存在"},
        409: {"description": "图书已被借出，无法删除"},
    },
)
async def delete_book(book_id: int = Path(..., gt=0, description="图书ID")):
    """
    删除图书
    
    **注意：** 已借出的图书无法删除
    
    **返回：**
    - 204: 删除成功（无响应体）
    - 404: 图书不存在
    - 409: 图书状态不允许删除
    """
    # 查找图书
    book_index = None
    for i, book in enumerate(books_db):
        if book.id == book_id:
            book_index = i
            break

    if book_index is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail=f"图书 ID {book_id} 不存在"
        )

    # 检查图书状态
    book = books_db[book_index]
    if book.status == BookStatus.BORROWED:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT, detail="图书已被借出，无法删除"
        )

    # 删除图书
    del books_db[book_index]

    # 204 状态码不返回任何内容
    return None


# ---------- 借阅管理（演示资源层级关系）----------


@app.get(
    "/api/v1/books/{book_id}/borrow-records",
    response_model=List[BorrowRecord],
    tags=["借阅管理"],
    summary="获取图书的借阅记录",
)
async def get_book_borrow_records(book_id: int = Path(..., gt=0)):
    """
    获取指定图书的所有借阅记录
    
    **演示：** RESTful 中的资源层级关系
    
    `/books/{book_id}/borrow-records` 表示：图书的借阅记录
    """
    # 检查图书是否存在
    book = find_book_by_id(book_id)
    if book is None:
        raise HTTPException(status_code=404, detail="图书不存在")

    # 获取该图书的借阅记录
    records = [r for r in borrow_records_db if r.book_id == book_id]

    return records


@app.post(
    "/api/v1/books/{book_id}/borrow",
    response_model=BorrowRecord,
    status_code=201,
    tags=["借阅管理"],
    summary="借阅图书",
)
async def borrow_book(
    book_id: int = Path(..., gt=0), borrow_data: BorrowCreate = None
):
    """
    借阅图书
    
    **流程：**
    1. 检查图书是否存在
    2. 检查图书是否可借
    3. 创建借阅记录
    4. 更新图书状态为"已借出"
    """
    global next_borrow_id

    # 检查图书
    book = find_book_by_id(book_id)
    if book is None:
        raise HTTPException(status_code=404, detail="图书不存在")

    if book.status != BookStatus.AVAILABLE:
        raise HTTPException(status_code=409, detail=f"图书状态为 {book.status}，无法借阅")

    # 创建借阅记录
    record = BorrowRecord(
        id=next_borrow_id,
        book_id=book_id,
        borrower_name=borrow_data.borrower_name,
        borrower_email=borrow_data.borrower_email,
        borrow_date=datetime.now(),
        return_date=None,
        status="borrowed",
    )

    borrow_records_db.append(record)
    next_borrow_id += 1

    # 更新图书状态
    for i, b in enumerate(books_db):
        if b.id == book_id:
            books_db[i] = b.copy(
                update={"status": BookStatus.BORROWED, "updated_at": datetime.now()}
            )
            break

    return record


@app.post(
    "/api/v1/books/{book_id}/return",
    response_model=BorrowRecord,
    tags=["借阅管理"],
    summary="归还图书",
)
async def return_book(book_id: int = Path(..., gt=0)):
    """
    归还图书
    
    **流程：**
    1. 查找该图书最近的借阅记录
    2. 更新借阅记录为"已归还"
    3. 更新图书状态为"可借"
    """
    # 检查图书
    book = find_book_by_id(book_id)
    if book is None:
        raise HTTPException(status_code=404, detail="图书不存在")

    if book.status != BookStatus.BORROWED:
        raise HTTPException(status_code=409, detail="图书未被借出")

    # 查找最近的借阅记录
    record = None
    for r in reversed(borrow_records_db):
        if r.book_id == book_id and r.status == "borrowed":
            record = r
            break

    if record is None:
        raise HTTPException(status_code=404, detail="未找到借阅记录")

    # 更新借阅记录
    record.return_date = datetime.now()
    record.status = "returned"

    # 更新图书状态
    for i, b in enumerate(books_db):
        if b.id == book_id:
            books_db[i] = b.copy(
                update={"status": BookStatus.AVAILABLE, "updated_at": datetime.now()}
            )
            break

    return record


# ---------- 统计信息 ----------


@app.get("/api/v1/stats", tags=["统计"])
async def get_statistics():
    """
    获取系统统计信息
    
    **演示：** 聚合数据的 API 设计
    """
    # 统计各分类的图书数量
    category_stats = {}
    for category in BookCategory:
        count = sum(1 for b in books_db if b.category == category)
        category_stats[category.value] = count

    # 统计各状态的图书数量
    status_stats = {}
    for status_enum in BookStatus:
        count = sum(1 for b in books_db if b.status == status_enum)
        status_stats[status_enum.value] = count

    return {
        "total_books": len(books_db),
        "total_borrow_records": len(borrow_records_db),
        "books_by_category": category_stats,
        "books_by_status": status_stats,
        "active_borrows": sum(1 for r in borrow_records_db if r.status == "borrowed"),
    }


# ========== 启动信息 ==========

if __name__ == "__main__":
    print("=" * 60)
    print("📚 图书管理系统 API")
    print("=" * 60)
    print("\n启动命令:")
    print(
        "uvicorn lessons.03_http_restful_api_practice:app --reload --port 8001\n"
    )
    print("访问地址:")
    print("- API 文档: http://127.0.0.1:8001/docs")
    print("- 根路径: http://127.0.0.1:8001/")
    print("- 健康检查: http://127.0.0.1:8001/health")
    print("\n" + "=" * 60)

